// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/arm-smccc.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/amlogic/efuse.h>
#include <linux/kallsyms.h>
#include "efuse.h"

static DEFINE_MUTEX(efuse_lock);

static unsigned long get_sharemem_info(unsigned long function_id)
{
    struct arm_smccc_res res;

    arm_smccc_smc((unsigned long)function_id, 0, 0, 0, 0, 0, 0, 0, &res);

    return res.a0;
}

static ssize_t meson_efuse_fn_smc(struct efuse_hal_api_arg *arg)
{
    long ret;
    unsigned int cmd, offset, size;
    unsigned long *retcnt = (unsigned long *)(arg->retcnt);
    struct arm_smccc_res res;
    void *sharemem_in_base = NULL;
    void *sharemem_out_base;
    long phy_in_base = 0;
    long phy_out_base = 0;

    if (arg->cmd == EFUSE_HAL_API_READ) {
        cmd = efuse_cmd.read_cmd;
    } else if (arg->cmd == EFUSE_HAL_API_WRITE) {
        cmd = efuse_cmd.write_cmd;
    } else {
        return -1;
    }

    offset = arg->offset;
    size = arg->size;

    mutex_lock(&efuse_lock);

    if (arg->cmd == EFUSE_HAL_API_WRITE) {
        phy_in_base = get_sharemem_info(efuse_cmd.mem_in_base_cmd);
        if (!pfn_valid(__phys_to_pfn(phy_in_base))) {
            sharemem_in_base = ioremap_nocache(phy_in_base, size);
        } else {
            sharemem_in_base = phys_to_virt(phy_in_base);
        }

        memcpy((void *)sharemem_in_base, (const void *)arg->buffer, size);
    }

    asm __volatile__("" : : : "memory");

    arm_smccc_smc(cmd, offset, size, 0, 0, 0, 0, 0, &res);
    ret = res.a0;
    *retcnt = res.a0;

    if (arg->cmd == EFUSE_HAL_API_WRITE) {
        if (!pfn_valid(__phys_to_pfn(phy_in_base))) {
            iounmap(sharemem_in_base);
        }
    } else if ((arg->cmd == EFUSE_HAL_API_READ) && (ret != 0)) {
        phy_out_base = get_sharemem_info(efuse_cmd.mem_out_base_cmd);
        if (!pfn_valid(__phys_to_pfn(phy_out_base))) {
            sharemem_out_base = ioremap_nocache(phy_out_base, ret);
        } else {
            sharemem_out_base = phys_to_virt(phy_out_base);
        }

        memcpy((void *)arg->buffer, (const void *)sharemem_out_base, ret);

        if (!pfn_valid(__phys_to_pfn(phy_out_base))) {
            iounmap(sharemem_out_base);
        }
    }

    mutex_unlock(&efuse_lock);

    if (!ret) {
        return -1;
    }

    return 0;
}

static ssize_t meson_trustzone_efuse(struct efuse_hal_api_arg *arg)
{
    ssize_t ret;
    struct cpumask task_cpumask;

    if (!arg) {
        return -1;
    }

    cpumask_copy(&task_cpumask, current->cpus_ptr);
    set_cpus_allowed_ptr(current, cpumask_of(0));

    ret = meson_efuse_fn_smc(arg);
    set_cpus_allowed_ptr(current, &task_cpumask);

    return ret;
}

static unsigned long efuse_data_process(unsigned long type, unsigned long buffer, unsigned long length,
                                        unsigned long option)
{
    struct arm_smccc_res res;
    void *sharemem_in_base;
    long phy_in_base;
    struct page *page;

    mutex_lock(&efuse_lock);

    phy_in_base = get_sharemem_info(efuse_cmd.mem_in_base_cmd);
    page = pfn_to_page(phy_in_base >> PAGE_SHIFT);

    if (!pfn_valid(__phys_to_pfn(phy_in_base))) {
        sharemem_in_base = ioremap_nocache(phy_in_base, length);
    } else {
        sharemem_in_base = phys_to_virt(phy_in_base);
    }

    memcpy((void *)sharemem_in_base, (const void *)buffer, length);

    asm __volatile__("" : : : "memory");

    do {
        arm_smccc_smc((unsigned long)AML_DATA_PROCESS, (unsigned long)type, (unsigned long)phy_in_base,
                      (unsigned long)length, (unsigned long)option, 0, 0, 0, &res);
    } while (0);

    if (!pfn_valid(__phys_to_pfn(phy_in_base))) {
        iounmap(sharemem_in_base);
    }

    mutex_unlock(&efuse_lock);

    return res.a0;
}

int efuse_amlogic_cali_item_read(unsigned int item)
{
    struct arm_smccc_res res;

    /* range check */
    if (item < EFUSE_CALI_SUBITEM_WHOBURN || item > EFUSE_CALI_SUBITEM_BC) {
        return -EINVAL;
    }

    mutex_lock(&efuse_lock);

    do {
        arm_smccc_smc((unsigned long)EFUSE_READ_CALI_ITEM, (unsigned long)item, 0, 0, 0, 0, 0, 0, &res);
    } while (0);

    mutex_unlock(&efuse_lock);
    return res.a0;
}
EXPORT_SYMBOL_GPL(efuse_amlogic_cali_item_read);

/*
 * retrun: 1: wrote, 0: not write, -1: fail or not support
 */
int efuse_amlogic_check_lockable_item(unsigned int item)
{
    struct arm_smccc_res res;

    /* range check */
    if (item < EFUSE_LOCK_SUBITEM_BASE || item > EFUSE_LOCK_SUBITEM_MAX) {
        return -EINVAL;
    }

    mutex_lock(&efuse_lock);

    do {
        arm_smccc_smc((unsigned long)EFUSE_READ_CALI_ITEM, (unsigned long)item, 0, 0, 0, 0, 0, 0, &res);
    } while (0);

    mutex_unlock(&efuse_lock);
    return res.a0;
}
EXPORT_SYMBOL_GPL(efuse_amlogic_check_lockable_item);

unsigned long efuse_amlogic_set(char *buf, size_t count)
{
    unsigned long ret;
    struct cpumask task_cpumask;

    cpumask_copy(&task_cpumask, current->cpus_ptr);
    set_cpus_allowed_ptr(current, cpumask_of(0));

    ret = efuse_data_process(AML_D_P_W_EFUSE_AMLOGIC, (unsigned long)buf, (unsigned long)count, 0);
    set_cpus_allowed_ptr(current, &task_cpumask);

    return ret;
}

static ssize_t meson_trustzone_efuse_get_max(struct efuse_hal_api_arg *arg)
{
    ssize_t ret;
    unsigned int cmd;
    struct arm_smccc_res res;

    if (arg->cmd != EFUSE_HAL_API_USER_MAX) {
        return -1;
    }

    cmd = efuse_cmd.get_max_cmd;

    asm __volatile__("" : : : "memory");
    arm_smccc_smc(cmd, 0, 0, 0, 0, 0, 0, 0, &res);
    ret = res.a0;

    if (!ret) {
        return -1;
    }

    return ret;
}

ssize_t efuse_get_max(void)
{
    struct efuse_hal_api_arg arg;
    ssize_t ret;
    struct cpumask task_cpumask;

    arg.cmd = EFUSE_HAL_API_USER_MAX;

    cpumask_copy(&task_cpumask, current->cpus_ptr);
    set_cpus_allowed_ptr(current, cpumask_of(0));

    ret = meson_trustzone_efuse_get_max(&arg);
    set_cpus_allowed_ptr(current, &task_cpumask);

    return ret;
}

static ssize_t _efuse_read(char *buf, size_t count, loff_t *ppos)
{
    unsigned int pos = *ppos;

    struct efuse_hal_api_arg arg;
    unsigned int retcnt;
    ssize_t ret;

    arg.cmd = EFUSE_HAL_API_READ;
    arg.offset = pos;
    arg.size = count;
    arg.buffer = (unsigned long)buf;
    arg.retcnt = (unsigned long)&retcnt;
    ret = meson_trustzone_efuse(&arg);
    if (ret == 0) {
        *ppos += retcnt;
        return retcnt;
    }

    return ret;
}

static ssize_t _efuse_write(const char *buf, size_t count, loff_t *ppos)
{
    unsigned int pos = *ppos;

    struct efuse_hal_api_arg arg;
    unsigned int retcnt;
    ssize_t ret;

    arg.cmd = EFUSE_HAL_API_WRITE;
    arg.offset = pos;
    arg.size = count;
    arg.buffer = (unsigned long)buf;
    arg.retcnt = (unsigned long)&retcnt;

    ret = meson_trustzone_efuse(&arg);
    if (ret == 0) {
        *ppos = retcnt;
        return retcnt;
    }

    return ret;
}

ssize_t efuse_read_usr(char *buf, size_t count, loff_t *ppos)
{
    char *pdata = NULL;
    ssize_t ret;
    loff_t pos;

    pdata = kmalloc(count, GFP_KERNEL);
    if (!pdata) {
        return -ENOMEM;
    }

    pos = *ppos;

    ret = _efuse_read(pdata, count, (loff_t *)&pos);

    memcpy(buf, pdata, count);
    kfree(pdata);

    return ret;
}

ssize_t efuse_write_usr(char *buf, size_t count, loff_t *ppos)
{
    char *pdata = NULL;
    ssize_t ret;
    loff_t pos;

    pdata = kmalloc(count, GFP_KERNEL);
    if (!pdata) {
        return -ENOMEM;
    }

    memcpy(pdata, buf, count);
    pos = *ppos;

    ret = _efuse_write(pdata, count, (loff_t *)&pos);
    kfree(pdata);

    return ret;
}
